# todo: draw against higher rated=some points

import operator
import sys
import glicko2
import timeit
import math
import time
import os
import numpy as np
import matplotlib.pyplot as plt
from pylab import *

# use: reader.py filename

d = dict() # dictionary of videos
matches = [] # list of matches

def newPlayer(name):
	d[name] = {'score':initscore,'occurs':1,'glicko':glicko2.Player()}

def loadResultsFile(filename):
	global matches
	global d
	
	d = dict() # dictionary of videos
	matches = [] # list of matches

	try:
		datafile = open(filename,'r')
	except:
		print "Error loading file: %s (May not exist.) Skipping..." % filename
		return False

	lastuser = -1
	usermatch = 0
	usercount = 1

	for line in datafile:
		words = line.split()
		if words[2] not in d:
			#d[words[2]] = {'score':initscore,'occurs':1}
			newPlayer(words[2])
		else:
			d[words[2]]['occurs'] = d[words[2]]['occurs']+1
		if words[3] not in d:
			#d[words[3]] = {'score':initscore,'occurs':1}
			newPlayer(words[3])
		else:
			d[words[3]]['occurs'] = d[words[3]]['occurs']+1

		user = words[0]
		if (lastuser != user):
			usercount = usercount + 1
			usermatch = 0
			lastuser = user
		else:
			usermatch = usermatch + 1

		m = {'time':words[1],'user':user,'usermatch':usermatch,'a':words[2],'b':words[3],'w':words[4]}
		matches.append(m)

	print "Loaded %d video contestants, in %d matches, by %d users, from %s" % (len(d),len(matches),usercount,filename)
	#default sort is by user, sort by time
	matches = sorted(matches,key=lambda k: k['time'])
	return True

#
def scoreMatchesGlicko2():
	sys.stdout.write('Calculating Glicko2 scores..')
	#reset 
	for (key,value) in d.iteritems():
		value['glicko'] = glicko2.Player()

	# list of players who compete in this cycle
	played_this_match = []
	timeset=False
	timestart = 0.0
	timenext = 0.0
	timebetweencycle = 86400  # 86400=seconds in a day
	modes = {"all","never","manual"}
	cyclemode = "never"
	cycles = 1
	nummatches = 0

	for m in matches:
		nummatches = nummatches + 1
		if (nummatches%100.0 == 0):
			sys.stdout.write('.')
			sys.stdout.flush()

		if (m['w']=='0'):
			Wa = 1.0
			Wb = 0.0
		elif (m['w']=='1'):
			Wa = 0.0
			Wb = 1.0
		else: #draw
			Wa = 0.5
			Wb = 0.5

		a = d[m['a']]['glicko']
		b = d[m['b']]['glicko']
		a.update_player([x for x in [b.getRating()]],[x for x in [b.getRd()]], [Wa])
		b.update_player([x for x in [a.getRating()]],[x for x in [a.getRd()]], [Wb])
		played_this_match.append(m['a'])
		played_this_match.append(m['b'])
		#2011/10/01-07:56:25.381623
		
		bCycle = False
		if (cyclemode == "all"):
			bCycle = True
		elif (cyclemode == "manual"):
			matchtime = time.strptime(m['time'][:18],"%Y/%m/%d-%H:%M:%S")
			matchsec = time.mktime(matchtime)
			if (timeset == False):
				timeset = True
				timestart = matchsec
				timenext = timestart+timebetweencycle
			elif (matchsec > timenext):
				bCycle = True
		#elif cyclemode == "never": #do nothing

		if (bCycle == True):
			timestart = matchsec
			timenext = timestart+timebetweencycle
			cycles = cycles + 1
			num = 0
			for (key,value) in d.iteritems():
				if key not in played_this_match:
					value['glicko'].did_not_compete()
					num = num + 1
			#print "%d didn't compete this cycle" % (num)
			played_this_match = [] #reset

	# assign rankings
	sorted_glicko = sorted(d.iteritems(),key=lambda (k,v): v['glicko'].getRating(), reverse=True)
	rank = 1
	for (key,value) in sorted_glicko:
		value['glickorank'] = rank
		rank = rank + 1

	print "Glicko2 completed with %d cycles" % cycles

def resetEloScores(initscore):
	for (key,value) in d.iteritems():
		value['score'] = initscore

def scoreMatchesElo(printUpsets,k_factor):
	#print "Calculating Elo Scores.."
	resetEloScores(initscore)
	for m in matches:
		# http://en.wikipedia.org/wiki/Elo_rating_system
		# ra: a rating. ea: a chance of winning (ea+eb=1). wa=1: a won. wa=2: skip
		ra = d[m['a']]['score']
		rb = d[m['b']]['score']
		ea = 1.0/(1.0+10.0**((rb-ra)/400.0))
		eb = 1.0/(1.0+10.0**((ra-rb)/400.0))
		if (m['w']=='0'):
			Wa = 1
			Wb = 0
		elif (m['w']=='1'):
			Wa = 0
			Wb = 1
		else:
			Wa = 2
			Wb = 2

		if Wa != 2:
			#print "a:"+str(ra)+" %:"+str(ea)+" b:"+str(rb)+" %:"+str(eb)
			ran = ra+k_factor*(Wa-ea)
			rbn = rb+k_factor*(Wb-eb)
			#if Wa == 1:
			#	print "a wins. a:"+str(ra)+"->"+str(ran)+". b:"+str(rb)+"->"+str(rbn)
			#if Wb == 1:
			#	print "b wins. a:"+str(ra)+"->"+str(ran)+". b:"+str(rb)+"->"+str(rbn)
			d[m['a']]['score'] = ran
			d[m['b']]['score'] = rbn

		if (printUpsets == True):
			if (ea > 0.7 and Wa == 0):
				print "b upset a! "+str(m)
				print "a:"+str(ra)+" %:"+str(ea)+" b:"+str(rb)+" %:"+str(eb)
			if (eb > 0.7 and Wb == 0):
				print "a upset b! "+str(m)
				print "a:"+str(ra)+" %:"+str(ea)+" b:"+str(rb)+" %:"+str(eb)

	#assign rankings
	sorted_elo = sorted(d.iteritems(),key=lambda (k,v): v['score'], reverse=True)
	rank = 1
	for (key,value) in sorted_elo:
		value['elorank'] = rank
		rank = rank + 1

def printResultsSortedByScore():
	sorted_d = sorted(d.iteritems(),key=lambda (k,v): v['score'])
	for s in sorted_d:
		if (s[1]['score'] > print_thresh):
			print s

def printGlicko2Results():
	sorted_d = sorted(d.iteritems(),key=lambda (k,v): v['glicko'].getRating())
	for (key,value) in sorted_d:
		prnt = "%s %f %f" % (key, value['glicko'].getRating(), value['glicko'].getRd())
		print prnt

def printCombinedResults():
	sorted_d = sorted(d.iteritems(),key=lambda (k,v): v['glicko'].getRating())
	for (key,value) in sorted_d:
		diff = math.fabs(value['glicko'].getRating()-value['score'])
		#if (diff > 400 or diff < -400):
		prnt = "%s glicko2:%f %f elo:%f diff:%f" % (key, value['glicko'].getRating(), value['glicko'].getRd(),value['score'],diff)
		print prnt

def printEntry(name):
	diff = math.fabs(d[name]['glicko'].getRating()-d[name]['score'])
	prnt = "%s glicko2:%f %f elo:%f diff:%f" % (name, d[name]['glicko'].getRating(), d[name]['glicko'].getRd(),d[name]['score'],diff)
	print prnt	

def bestFitKFactor(score1,score2,scorestep,k1,k2,kstep):
	#scoreMatchesGlicko2()
	lowk = 0.0
	lowdiff = 99999.0
	lowscore = 0.0
	print "Calculating similar k-factor.."
	
	def my_range(start, end, step):
		while start <= end:
			yield start
			start += step

	for iscore in my_range(score1,score2, scorestep):
		for x in my_range(k1,k2,kstep):
			scoreMatchesElo(False,x)
			highd = calcRankDiff()
			
			if False:
				highd = 0.0

				for (key,value) in d.iteritems():
					diff = math.fabs(value['glicko'].getRating()-value['score'])
					if diff > highd:
						highd = diff
			
			if highd < lowdiff:
				lowdiff = highd
				lowk = x
				lowscore = iscore

			#print "highd for k %f: %f" % (x,highd)

	#print "k from %f to %f -- score from: %f to %f" % (k1,k2,score1,score2)
	#print "low d: %f k: %f score: %f" % (lowdiff,lowk,lowscore)

	return lowk

def checkEloInGlickoRange():
	print "Checking for Elo scores out of Glicko2 range"
	for (key,value) in d.iteritems():
		diff = math.fabs(value['glicko'].getRating()-value['score'])
		if (diff > (2*value['glicko'].getRd())):
			print('out of range:'),
			printEntry(key)

def printTopRankDiff():
	sorted_glicko = sorted(d.iteritems(),key=lambda (k,v): v['glicko'].getRating(), reverse=True)
	rank = 1
	for (key,value) in sorted_glicko:
		elorank = value['elorank']
		just = math.fabs(1.0/rank-1.0/elorank)
		if (rank < 4 or just > 0.3):
			print "grank: %d erank: %d injust: %f" % (rank,elorank,just)
		rank = rank + 1
	
def calcRankDiff():
	ranksum = 0.0
	
	for (key,value) in d.iteritems():
		grank = value['glickorank']
		elorank = value['elorank']
		rankdiff = math.fabs(1.0/grank - 1.0/elorank)
		#if (rankdiff > 0.8):
		#	print "rank diff %f on rank %d" % (rankdiff,rank)
		ranksum = ranksum + rankdiff

	#print "\nElo-Glicko2 rank diff (sum|1/grank-1/erank|) injust: %f" % ranksum
	return ranksum
	
	
#main
initscore = 1500.0
#k = 190.0
print_thresh = 00.0
x = []
y = []
y2 = []
y3 = []

if (len(sys.argv) < 2):
	print "usage: python reader.py <datafile>"
	sys.exit()

week = 1

directory = os.path.join(".",sys.argv[1])
for root,dirs,files in os.walk(directory):
	for file in files:
		if file.endswith(".txt"):
			filename = os.path.join(directory,file)
			if loadResultsFile(filename):
				scoreMatchesGlicko2()
				k=bestFitKFactor(1500,1500,1,15,335,10)
				scoreMatchesElo(False,k)
				injust = calcRankDiff()

				#printResultsSortedByScore()
				#printGlicko2Results()
				#printCombinedResults()
				#checkEloInGlickoRange()
				#printTopRankDiff()
				

				sorted_items = sorted(d.iteritems(),key=lambda (k,v): v['glicko'].getRating(), reverse=True)
				#sorted_items = sorted(d.iteritems(),key=lambda (k,v): v['score'], reverse=True)
				rank = 0.0
				f2x = [] 
				f2y = []
				f2y2 = []
				f2yerr = []
				f2yerr2 = []
				f2yout = []
				f2xout = []
				f2y3 = [] #injust
				for (key,value) in sorted_items:
					rank = rank + 1.0
					f2x.append(rank)
					erank = value['elorank']
					erate = value['score']
					grate = value['glicko'].getRating()
					grd = value['glicko'].getRd()
					f2y.append(grate)
					f2yerr.append(grate-(2*grd))
					f2yerr2.append(grate+(2*grd))
					f2y2.append(erate)
					if (grate+2*grd < erate or grate-2*grd > erate):
						f2xout.append(rank)
						f2yout.append(erate)
					f2y3.append(math.fabs(1.0/rank - 1.0/erank))

				plt.subplot(2,1,1)
				p0 = plt.plot(f2x,f2y,'b-')
				p1 = plt.plot(f2x,f2yerr,'b-')
				p2 = plt.plot(f2x,f2yerr2,'b-')
				p3 = plt.plot(f2x,f2y2,'g-')
				p4 = plt.plot(f2xout,f2yout,'ro')
				plt.ylabel('rating')
				plt.xlabel('rank')
				plt.legend( (p0[0], p3[0],p4[0]), ('glicko2', 'elo','elo>g+rd') )
				plt.title("%s k=%d" % (filename,k))
				plt.subplot(2,1,2)
				p5 = plt.plot(f2x,f2y3,'r-')
				plt.ylabel('injust')
				show()
				
				#scoreMatchesElo(False,35)
				#y3.append(calcRankDiff())

				y.append(injust)
				y2.append(k)
				week = week + 1
				x.append(week)
				print "-"
				
if False:
	plt.figure(1)
	plt.subplot(2,1,1)
	plt.plot(x, y, '-')
	plt.ylabel('injust')
	plt.xlabel('week')
	plt.subplot(2,1,2)
	plt.plot(x, y2, 'r-')
	plt.ylabel('k')
	show()

#comapre comedy to dance etc
#diff in sensitivity to permutations of ordering
#elo in glicko range: low-rd mid-score
# sum | 1/i-1/j | over items rank i and j for same item